﻿using System.Collections.Generic;
using System.IO;

namespace Game.Lib.CSG
{
    public class Triangle : Shape2D
    {
        public Vector2 P0;
        public Vector2 P1;
        public Vector2 P2;

        public override Vector2 Pos
        {
            get
            {
                return (P0 + P1 + P2) / 3f;
            }
            set
            {
                Vector2 p = Pos;

                Vector2 d0 = P0 - p;
                Vector2 d1 = P1 - p;
                Vector2 d2 = P2 - p;

                P0 = d0 + value;
                P1 = d1 + value;
                P2 = d2 + value;
            }
        }

        public override float Rot
        {
            get
            {
                return _Rot;
            }
            set
            {
                _Rot += value;

                Vector2 delta = new Vector2(0f, value);

                P0 = Vector2.FromPolar((P0 - Pos).ToPolar() + delta);
                P1 = Vector2.FromPolar((P1 - Pos).ToPolar() + delta);
                P2 = Vector2.FromPolar((P2 - Pos).ToPolar() + delta);
            }
        }

        public override Vector2 Size
        {
            get
            {
                return base.Size;
            }
            set
            {
                base.Size = value;
            }
        }

        public override float Area
        {
            get { return .5f * ((P1 - P0) % (P2 - P0)); }
        }

        public override float Perimeter
        {
            get { return Vector2.Distance(P0, P1) + Vector2.Distance(P1, P2) + Vector2.Distance(P2, P0); }
        }

        public LineSegment SideA
        {
            get
            {
                return new LineSegment(P0, P1);
            }
            set
            {
                P0 = value.Start;
                P1 = value.End;
            }
        }

        public LineSegment SideB
        {
            get
            {
                return new LineSegment(P1, P2);
            }
            set
            {
                P1 = value.Start;
                P2 = value.End;
            }
        }

        public LineSegment SideC
        {
            get
            {
                return new LineSegment(P2, P0);
            }
            set
            {
                P2 = value.Start;
                P0 = value.End;
            }
        }

        public override List<Vector> CriticalPoints
        {
            get
            {
                List<Vector> ret = new List<Vector>(3);
                ret.Add(P0);
                ret.Add(P1);
                ret.Add(P2);
                return ret;
            }
        }

        private float _Rot;

        public Triangle()
        {
            P0 = P1 = P2 = Vector2.Zero;
        }

        public Triangle(Vector2 a, Vector2 b, Vector2 c)
        {
            P0 = a;
            P1 = b;
            P2 = c;
        }

        public Triangle(LineSegment a, LineSegment b, LineSegment c)
        {
            if(a.Length > 0f && b.Length > 0f && c.Length > 0f)
            {
                if (sharedVertex(a, b) && sharedVertex(b, c) && sharedVertex(a, c) && !degen(a, b, c))
                {
                    Vector2[] t = pick3(a, b, c);
                    P0 = t[0];
                    P1 = t[1];
                    P2 = t[2];
                }
            }
        }

        public Triangle(LineSegment ls, Vector2 p)
        {
            P0 = ls.Start;
            P1 = ls.End;
            P2 = p;
        }

        public override Rectangle GetBoundingRectangle()
        {
            Vector2 min = Vector2.Min(P0, Vector2.Min(P1, P2));
            Vector2 max = Vector2.Max(P0, Vector2.Max(P1, P2));
            return new Rectangle((min + max) * .5f, max - min);
        }

        public override Ellipse GetBoundingCircle()
        {
            Line pb0 = new Line(SideA.MidPoint, -1f / SideA.Slope);
            Line pb1 = new Line(SideB.MidPoint, -1f / SideB.Slope);
            Vector2 center = Line.Intersection(pb0, pb1);
            return new Ellipse(center, Vector2.Distance(center, P0));
        }

        public override bool Inside(Vector2 pos)
        {
            //Barycentric method
            //should be pretty fast
            Vector2 v0 = P2 - P0;
            Vector2 v1 = P1 - P0;
            Vector2 v2 = pos - P0;

            float dot00 = v0 * v0;
            float dot01 = v0 * v1;
            float dot02 = v0 * v2;
            float dot11 = v1 * v1;
            float dot12 = v1 * v2;

            float denom = 1f / (dot00 * dot11 - dot01 * dot01);
            float u = (dot11 * dot02 - dot01 * dot12) * denom;
            float v = (dot00 * dot12 - dot01 * dot02) * denom;

            // Check if point is in triangle
            return (u > 0f) && (v > 0f) && (u + v < 1f);
        }

        private bool sharedVertex(LineSegment a, LineSegment b)
        {
            return a.Start == b.Start || a.Start == b.End || a.End == b.Start || a.End == b.End;
        }

        private bool degen(LineSegment a, LineSegment b, LineSegment c)
        {
            return all3(a.Start, b.Start, c.Start) ||
                   all3(a.Start, b.Start, c.End) ||
                   all3(a.Start, b.End, c.Start) ||
                   all3(a.Start, b.End, c.End) ||
                   all3(a.End, b.Start, c.Start) ||
                   all3(a.End, b.Start, c.End) ||
                   all3(a.End, b.End, c.Start) ||
                   all3(a.End, b.End, c.End);
        }

        private bool all3(Vector2 a, Vector2 b, Vector2 c)
        {
            return a == b && b == c;
        }

        private Vector2[] pick3(LineSegment a, LineSegment b, LineSegment c)
        {
            List<Vector2> test = new List<Vector2>();
            test.Add(a.Start);
            test.Add(a.End);
            if (!test.Exists(x => x == b.Start)) { test.Add(b.Start); return test.ToArray(); }
            if (!test.Exists(x => x == b.End)) { test.Add(b.Start); return test.ToArray(); }
            if (!test.Exists(x => x == c.Start)) { test.Add(b.Start); return test.ToArray(); }
            test.Add(c.End);
            return test.ToArray();
        }

        public override void Serialize(Stream s)
        {
            P0.Serialize(s);
            P1.Serialize(s);
            P2.Serialize(s);
        }

        public override void Deserialize(Stream s)
        {
            P0.Deserialize(s);
            P1.Deserialize(s);
            P2.Deserialize(s);
        }
    }
}
